package com.opNaga.bpmp;

import com.opNaga.bpmp.artifact.Annotation;
import com.opNaga.bpmp.artifact.DataObject;
import com.opNaga.bpmp.artifact.Group;
import com.opNaga.bpmp.connectingObject.ConnectingObject;
import com.opNaga.bpmp.connectingObject.ConnectingObjectSource;
import com.opNaga.bpmp.connectingObject.ConnectingObjectTarget;
import com.opNaga.bpmp.connectingObject.association.AssociationFlow;
import com.opNaga.bpmp.connectingObject.association.AssociationFlowSource;
import com.opNaga.bpmp.connectingObject.association.AssociationFlowTarget;
import com.opNaga.bpmp.connectingObject.messageFlow.MessageFlow;
import com.opNaga.bpmp.connectingObject.messageFlow.MessageFlowSource;
import com.opNaga.bpmp.connectingObject.messageFlow.MessageFlowTarget;
import com.opNaga.bpmp.connectingObject.sequenceFlow.SequenceFlow;
import com.opNaga.bpmp.connectingObject.sequenceFlow.SequenceFlowSource;
import com.opNaga.bpmp.connectingObject.sequenceFlow.SequenceFlowTarget;
import com.opNaga.bpmp.flowObject.activity.SubProcess;
import com.opNaga.bpmp.flowObject.activity.Task;
import com.opNaga.bpmp.flowObject.event.EndEvent;
import com.opNaga.bpmp.flowObject.event.IntermediateEvent;
import com.opNaga.bpmp.flowObject.event.StartEvent;
import com.opNaga.bpmp.flowObject.gateway.ANDGateway;
import com.opNaga.bpmp.flowObject.gateway.ORGateway;
import com.opNaga.bpmp.flowObject.gateway.XORGateway;
import com.opNaga.bpmp.swimlane.Containable;
import com.opNaga.bpmp.swimlane.Container;
import com.opNaga.bpmp.swimlane.Lane;
import com.opNaga.bpmp.swimlane.Pool;

import java.io.File;
import java.util.*;

/**
 * User: Junyi BEI
 * Date: 30/01/2012
 * Time: 19:26
 */
public class BPMPManager {
  public static Dictionary<String, String> shortcuts = new Hashtable<String, String>();

  static {
    shortcuts.put("ne", "name");
    shortcuts.put("n", "name");
    shortcuts.put("es", "elements");
    shortcuts.put("pl", "pool");
    shortcuts.put("p", "pool");
    shortcuts.put("s", "source");
    shortcuts.put("se", "source");
    shortcuts.put("t", "target");
    shortcuts.put("tt", "target");
    shortcuts.put("in", "container");
    shortcuts.put("cr", "container");
    shortcuts.put("hl", "horizontal");
    shortcuts.put("sl", "single");
    shortcuts.put("ml", "multiple");
    shortcuts.put("isfss", "inwardsequenceflowsources");
    shortcuts.put("inwards", "inwardsequenceflowsources");
    shortcuts.put("ins", "inwardsequenceflowsources");
    shortcuts.put("is", "inwardsequenceflowsources");
    shortcuts.put("osfts", "outwardsequenceflowtargets");
    shortcuts.put("outwards", "outwardsequenceflowtargets");
    shortcuts.put("outs", "outwardsequenceflowtargets");
    shortcuts.put("os", "outwardsequenceflowtargets");
    shortcuts.put("imfss", "inwardmessageflowsources");
    shortcuts.put("im", "inwardmessageflowsources");
    shortcuts.put("omfts", "outwardmessageflowtargets");
    shortcuts.put("om", "outwardmessageflowtargets");
    shortcuts.put("iass", "inwardassociationsources");
    shortcuts.put("ia", "inwardassociationsources");
    shortcuts.put("oats", "outwardassociationtargets");
    shortcuts.put("oa", "outwardassociationtargets");
  }

  private Dictionary<String, BPMPElement> dictionary = new Hashtable<String, BPMPElement>();
  private Dictionary<String, Integer> index = new Hashtable<String, Integer>();
  private Dictionary<Class, Integer> counter = new Hashtable<Class, Integer>();
  private Dictionary<Class, SortedSet<Integer>> leftover = new Hashtable<Class, SortedSet<Integer>>();
  private Collection<BPMPElement> pools = new HashSet<BPMPElement>();

  public BPMPElement getInstance(String n) {
    return dictionary.get(n);
  }

  public BPMPElement[] getInstances(String... ts) {
    return getInstances(0, ts);
  }

  public BPMPElement[] getInstances(int s, String... ts) {
    Collection<BPMPElement> ec = new ArrayList<BPMPElement>();
    int l = ts.length;
    for(int i = s; i < l; i++) {
      ec.add(dictionary.get(ts[i]));
    }
    int en = ec.size();
    BPMPElement[] es = new BPMPElement[en];
    ec.toArray(es);
    return es;
  }

  public BPMPElement createInstance(String... t) {
    for(String ii : t) {
      System.out.print(ii + " ");
    }
    System.out.println();
    int len = t.length;
    if(len < 1) {
      return null;
    }
    Dictionary<String, String> paras = new Hashtable<String, String>();
    for(int i = 1; i < len; i++) {
      String read = t[i].trim();
      if(read.startsWith("-") || read.startsWith("/")) {
        String[] split = read.split("=");
        String var = split[0].substring(1).trim().toLowerCase();
        String varFull = shortcuts.get(var);
        if(varFull != null) {
          var = varFull;
        }
        String val;
        if(split.length >= 2) {
          val = split[1].trim();
          if(val.equals("") || val.equalsIgnoreCase("t") || val.equalsIgnoreCase("y") || val.equalsIgnoreCase("yes") || val.equalsIgnoreCase("true") || val.equalsIgnoreCase("ture")) {
            val = "true";
          } else if(val.equalsIgnoreCase("n") || val.equalsIgnoreCase("no") || val.equalsIgnoreCase("f") || val.equalsIgnoreCase("false")) {
            val = "false";
          }
        } else {
          val = "true";
        }
        paras.put(var, val);
      }
    }
    String name = paras.get("name");
    BPMPElement be;
    if(t[0].equalsIgnoreCase("Pool") || t[0].equalsIgnoreCase("p")) {
      if(name == null) {
        Class c = Pool.class;
        int i = getIndexNumber(c);
        name = "p" + i;
        index.put(name, i);
      }
      be = new Pool(name);
      be.setFactory(this);
      pools.add(be);
    } else if(t[0].equalsIgnoreCase("Lane") || t[0].equalsIgnoreCase("l")) {
      if(name == null) {
        Class c = Lane.class;
        int i = getIndexNumber(c);
        name = "l" + i;
        index.put(name, i);
      }
      be = new Lane(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("AND Gateway") || t[0].equalsIgnoreCase("AND_Gateway") || t[0].equalsIgnoreCase("ANDGateway") || t[0].equalsIgnoreCase("ag")) {
      if(name == null) {
        Class c = ANDGateway.class;
        int i = getIndexNumber(c);
        name = "ag" + i;
        index.put(name, i);
      }
      be = new ANDGateway(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("OR Gateway") || t[0].equalsIgnoreCase("OR_Gateway") || t[0].equalsIgnoreCase("ORGateway") || t[0].equalsIgnoreCase("og")) {
      if(name == null) {
        Class c = ORGateway.class;
        int i = getIndexNumber(c);
        name = "og" + i;
        index.put(name, i);
      }
      be = new ORGateway(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("XOR Gateway") || t[0].equalsIgnoreCase("XOR_Gateway") || t[0].equalsIgnoreCase("XORGateway") || t[0].equalsIgnoreCase("xg")) {
      if(name == null) {
        Class c = XORGateway.class;
        int i = getIndexNumber(c);
        name = "xg" + i;
        index.put(name, i);
      }
      be = new XORGateway(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("Start Event") || t[0].equalsIgnoreCase("Start_Event") || t[0].equalsIgnoreCase("StartEvent") || t[0].equalsIgnoreCase("se")) {
      if(name == null) {
        Class c = StartEvent.class;
        int i = getIndexNumber(c);
        name = "se" + i;
        index.put(name, i);
      }
      be = new StartEvent(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("Intermediate Event") || t[0].equalsIgnoreCase("Intermediate_Event") || t[0].equalsIgnoreCase("IntermediateEvent") || t[0].equalsIgnoreCase("ie")) {
      if(name == null) {
        Class c = IntermediateEvent.class;
        int i = getIndexNumber(c);
        name = "ie" + i;
        index.put(name, i);
      }
      be = new IntermediateEvent(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("End Event") || t[0].equalsIgnoreCase("End_Event") || t[0].equalsIgnoreCase("EndEvent") || t[0].equalsIgnoreCase("ee")) {
      if(name == null) {
        Class c = EndEvent.class;
        int i = getIndexNumber(c);
        name = "ee" + i;
        index.put(name, i);
      }
      be = new EndEvent(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("Task") || t[0].equalsIgnoreCase("t")) {
      if(name == null) {
        Class c = Task.class;
        int i = getIndexNumber(c);
        name = "t" + i;
        index.put(name, i);
      }
      be = new Task(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("Sub Process") || t[0].equalsIgnoreCase("Sub_Process") || t[0].equalsIgnoreCase("SubProcess") || t[0].equalsIgnoreCase("sp")) {
      if(name == null) {
        Class c = SubProcess.class;
        int i = getIndexNumber(c);
        name = "sp" + i;
        index.put(name, i);
      }
      be = new SubProcess(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("Sequence Flow") || t[0].equalsIgnoreCase("Sequence_Flow") || t[0].equalsIgnoreCase("SequenceFlow") || t[0].equalsIgnoreCase("sf")) {
      if(name == null) {
        Class c = SequenceFlow.class;
        int i = getIndexNumber(c);
        name = "sf" + i;
        index.put(name, i);
      }
      be = new SequenceFlow(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("Message Flow") || t[0].equalsIgnoreCase("Message_Flow") || t[0].equalsIgnoreCase("MessageFlow") || t[0].equalsIgnoreCase("mf")) {
      if(name == null) {
        Class c = MessageFlow.class;
        int i = getIndexNumber(c);
        name = "mf" + i;
        index.put(name, i);
      }
      be = new MessageFlow(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("Association") || t[0].equalsIgnoreCase("af")) {
      if(name == null) {
        Class c = AssociationFlow.class;
        int i = getIndexNumber(c);
        name = "af" + i;
        index.put(name, i);
      }
      be = new AssociationFlow(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("Group") || t[0].equalsIgnoreCase("g")) {
      if(name == null) {
        Class c = Group.class;
        int i = getIndexNumber(c);
        name = "g" + i;
        index.put(name, i);
      }
      String gm = paras.get("gm");
      String[] gs = gm.split("&");
      BPMPElement[] es = getInstances(1, gs);
      be = new Group(name, es);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("Data Object") || t[0].equalsIgnoreCase("Data_Object") || t[0].equalsIgnoreCase("DataObject") || t[0].equalsIgnoreCase("do")) {
      if(name == null) {
        Class c = DataObject.class;
        int i = getIndexNumber(c);
        name = "do" + i;
        index.put(name, i);
      }
      be = new DataObject(name);
      be.setFactory(this);
    } else if(t[0].equalsIgnoreCase("Annotation") || t[0].equalsIgnoreCase("a")) {
      if(name == null) {
        Class c = Annotation.class;
        int i = getIndexNumber(c);
        name = "a" + i;
        index.put(name, i);
      }
      be = new Annotation(name);
      be.setFactory(this);
    } else {
      return null;
    }
    if(name != null && be != null) {
      Class beClass = be.getClass();
      if(Pool.class.isAssignableFrom(beClass)) {

      }
      if(Lane.class.isAssignableFrom(beClass)) {
        String poolName = paras.get("pool");
        if(poolName != null) {
          BPMPElement pool = dictionary.get(poolName);
          ((Pool) pool).addLane(be);
        }
      }
      if(Container.class.isAssignableFrom(beClass)) {
        String eNames = paras.get("elements");
        if(eNames != null) {
          String[] eList = eNames.split("&");
          BPMPElement[] es = getInstances(eList);
          for(BPMPElement e : es) {
            ((Container) be).addElement(e);
          }
        }
      }
      if(Containable.class.isAssignableFrom(beClass)) {
        String containerName = paras.get("container");
        if(containerName != null) {
          BPMPElement container = dictionary.get(containerName);
          ((Container) container).addElement(be);
        }
      }
      if(SequenceFlowTarget.class.isAssignableFrom(beClass)) {
        String isfsNames = paras.get("inwardsequenceflowsources");
        if(isfsNames != null) {
          String[] isfsList = isfsNames.split("&");
          BPMPElement[] isfss = getInstances(isfsList);
          for(BPMPElement isfs : isfss) {
            BPMPElement sf = createInstance("sf");
            ((SequenceFlow) sf).setSource(isfs);
            ((SequenceFlow) sf).setTarget(be);
          }
        }
      }
      if(SequenceFlowSource.class.isAssignableFrom(beClass)) {
        String osftNames = paras.get("outwardsequenceflowtargets");
        if(osftNames != null) {
          String[] osftList = osftNames.split("&");
          BPMPElement[] osfts = getInstances(osftList);
          for(BPMPElement osft : osfts) {
            BPMPElement sf = createInstance("sf");
            ((SequenceFlow) sf).setSource(be);
            ((SequenceFlow) sf).setTarget(osft);
          }
        }
      }
      if(MessageFlowTarget.class.isAssignableFrom(beClass)) {
        String imfsNames = paras.get("inwardmessageflowsources");
        if(imfsNames != null) {
          String[] imfsList = imfsNames.split("&");
          BPMPElement[] imfss = getInstances(imfsList);
          for(BPMPElement imfs : imfss) {
            BPMPElement mf = createInstance("mf");
            ((MessageFlow) mf).setSource(imfs);
            ((MessageFlow) mf).setTarget(be);
          }
        }
      }
      if(MessageFlowSource.class.isAssignableFrom(beClass)) {
        String omftNames = paras.get("outwardmessageflowtargets");
        if(omftNames != null) {
          String[] omftList = omftNames.split("&");
          BPMPElement[] omfts = getInstances(omftList);
          for(BPMPElement omft : omfts) {
            BPMPElement mf = createInstance("mf");
            ((MessageFlow) mf).setSource(be);
            ((MessageFlow) mf).setTarget(omft);
          }
        }
      }
      if(AssociationFlowTarget.class.isAssignableFrom(beClass)) {
        String iasNames = paras.get("inwardassociationsources");
        if(iasNames != null) {
          String[] iasList = iasNames.split("&");
          BPMPElement[] iass = getInstances(iasList);
          for(BPMPElement ias : iass) {
            BPMPElement af = createInstance("af");
            ((AssociationFlow) af).setSource(ias);
            ((AssociationFlow) af).setTarget(be);
          }
        }
      }
      if(AssociationFlowSource.class.isAssignableFrom(beClass)) {
        String oatNames = paras.get("outwardassociationtargets");
        if(oatNames != null) {
          String[] oatList = oatNames.split("&");
          BPMPElement[] oats = getInstances(oatList);
          for(BPMPElement oat : oats) {
            BPMPElement af = createInstance("af");
            ((AssociationFlow) af).setSource(be);
            ((AssociationFlow) af).setTarget(oat);
          }
        }
      }
      if(ConnectingObject.class.isAssignableFrom(beClass)) {
        String sourceName = paras.get("source");
        if(sourceName != null) {
          BPMPElement source = dictionary.get(sourceName);
          ((ConnectingObject) be).setSource(source);
        }
        String targetName = paras.get("target");
        if(targetName != null) {
          BPMPElement target = dictionary.get(targetName);
          ((ConnectingObject) be).setTarget(target);
        }
      }
      dictionary.put(name, be);
      System.out.println("added " + name);
    }
    return be;
  }

  public boolean renameInstance(BPMPElement be, String n) {
    if(dictionary.get(n) != null) {
      return false;
    }
    Class c = be.getClass();
    String text = be.getName();
    Integer i = index.remove(text);
    SortedSet<Integer> lis = leftover.get(c);
    if(lis == null) {
      lis = new TreeSet<Integer>();
      leftover.put(c, lis);
    }
    lis.add(i);
    dictionary.remove(text);
    dictionary.put(n, be);
    return true;
  }

  public boolean removeInstance(BPMPElement be) {
    Class c = be.getClass();
    String text = be.getName();
    Integer i = index.remove(text);
    SortedSet<Integer> lis = leftover.get(c);
    if(lis == null) {
      lis = new TreeSet<Integer>();
      leftover.put(c, lis);
    }
    lis.add(i);
    dictionary.remove(text);
    return true;
  }

  public BPMPElement getLink(BPMPElement from, BPMPElement to) {
    if(ConnectingObjectSource.class.isAssignableFrom(from.getClass()) && ConnectingObjectTarget.class.isAssignableFrom(to.getClass())) {
      return ((ConnectingObjectSource) from).getLinkTo(to);
    }
    System.out.println("failed getLink()");
    return null;
  }
  
  public String toSMV() {
    BPMPElement[] poolArray = new BPMPElement[pools.size()];
    pools.toArray(poolArray);
    return BPMPTranslator.encode(poolArray);
  }

  public File toFile(String fileName) {
    BPMPElement[] poolArray = new BPMPElement[pools.size()];
    pools.toArray(poolArray);
    return BPMPTranslator.encode(fileName, poolArray);
  }
  
  public int getBound() {
    int bound = 0;
    for(BPMPElement pool : pools) {
      bound += ((Pool) pool).getAtomicElements().size();
    }
    return bound;
  }

  private int getIndexNumber(Class c) {
    Integer l = null;
    SortedSet<Integer> lis = leftover.get(c);
    if(lis == null) {
      lis = new TreeSet<Integer>();
      leftover.put(c, lis);
    } else {
      if(!lis.isEmpty()) {
        l = lis.first();
      }
    }
    Integer i = counter.get(c);
    if(i == null) {
      i = 1;
      counter.put(c, i);
    }
    if(l != null && l < i) {
      lis.remove(l);
      return l;
    }
    Integer j = i + 1;
    counter.put(c, j);
    return i;
  }
  
}
